說到Middleware,一定要提一下核心物件HttpContext物件
回顧一下昨天提到的圖
在圖中可以看到Middleware是一棒傳一棒往下傳遞HttpRequest
跟HttpResponse
但實際上所傳遞的並不是HttpRequest
跟HttpResponse
而是HttpContxt
物件,裏面包含了HttpRequest
跟HttpResponse
還有一些其他的資訊
在介紹HttpContext的屬性前,我們先介紹一個比較簡單的Middleware使用方式吧
實際上Middleware就是一個RequestDelegate
的委派RequestDelegate.cs
public delegate Task RequestDelegate(HttpContext context);
正如前面所提的參數為HttpContext
來看個比較簡單的用法吧
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// 自訂Middleware,當HttpMethod 為Get的時候固定回傳"It a GET request"
// 其他方法則繼續往下走,並竄改回應
app.Use(async (context, next) =>
{
if (context.Request.Method == "GET")
{
await context.Response.WriteAsync("It a GET request");
return;
}
await next();
await context.Response.WriteAsync("回應已被竄改");
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
在這邊使用dotnet 6 minimal api 作為範例
插入了一個middleware
app.Use(async (context, next) =>
{
if (context.Request.Method == "GET")
{
await context.Response.WriteAsync("It a GET request");
return;
}
await next();
await context.Response.WriteAsync("回應已被竄改");
});
context 是HttpContext
物件next
就是上面提到的RequestDelegate
,當呼叫next()
或使用next.Invoke()
的時候
會將管線交給下一棒
因此,通常會在next()
前處理請求, next()
處理回應
app.Use(async (context, next) =>
{
// handle request
await next();
// handle response
});
在這個middleware中,http方法為 Get
的請求會被我攔截不往後傳遞
也就是不會進到Controller中
回應則會被竄改
其他Middleware的建立方式我打算留到明天XD
HttpContxt.cs
public abstract class HttpContext
{
public abstract IFeatureCollection Features { get; }
public abstract HttpRequest Request { get; }
public abstract HttpResponse Response { get; }
public abstract ConnectionInfo Connection { get; }
public abstract WebSocketManager WebSockets { get; }
public abstract ClaimsPrincipal User { get; set; }
public abstract IDictionary<object, object?> Items { get; set; }
public abstract IServiceProvider RequestServices { get; set; }
public abstract CancellationToken RequestAborted { get; set; }
public abstract string TraceIdentifier { get; set; }
public abstract ISession Session { get; set; }
public abstract void Abort();
}
因為大部分屬性都如字面上的意思,所以並不打算每個都介紹
IServiceProvider
,用來提供當前Request所需的服務當有些資訊你在前面的Middleware中取得了且後面不想花功夫再拿的時候可以使用
舉例,在middle的其中一步使用到了secret
且後續另個middleware也需要secret時,可以丟進items
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<FakeRepository>();
var count = 0;
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.Use(async (context, next) =>
{
var requestCount = Interlocked.Increment(ref count);
requestCount %= 3;
var fakeRepository = context.RequestServices.GetRequiredService<FakeRepository>();
var requestSecret = fakeRepository.Get(requestCount);
context.Items.Add(nameof(requestSecret), requestSecret);
await next();
});
app.Use(async (context, next) =>
{
var requestSecret = context.Items["requestSecret"] as string;
if (requestSecret != "Two")
{
context.Response.StatusCode = 401;
return;
}
await next();
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
public class FakeRepository
{
private new Dictionary<int, string> _fakeData = new()
{
{0,"Zero"},
{ 1, "One" },
{ 2, "Two" },
};
public string Get(int id)
{
return _fakeData[id];
}
}